/*
 * This file is part of the Sx Framework Library.
 * 
 * Copyright (C) 2013 University of Colorado Denver
 * <min.choi@ucdenver.edu> <shane.transue@ucdenver.edu>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 */
#include <sxOBJ_File.h>
#include <sxLineParser.h>
#include <sxMath.h>
#include <sxCharacter.h>
#include <sxScanner.h>
#include <sxOBJ_FileDefinition.h>

#include <fstream>
#include <iomanip>

const Sx::String Sx::Graphics::File::OBJ_File::OBJ_FILE_EXTENSION = "obj";

/*
 * Constant character delimiters and numerical constants defined by the
 * OBJ file format.
 */
const static int OBJ_INVALID_FACE_INDEX = -1;
const static char OBJ_NODE_DELIMITER = '/';
const static char OBJ_COMMENT = '#';
const static unsigned int OBJ_INDEX_OFFSET = 1;
const static Sx::String OBJ_DELIMITER = " ";
const static Sx::String OBJ_OBJECT = "o";
const static Sx::String OBJ_GROUP = "g";
const static Sx::String OBJ_SMOOTHING_GROUP = "s";
const static Sx::String OBJ_FACE = "f";
const static Sx::String OBJ_VERTEX_NORMAL = "vn";
const static Sx::String OBJ_VERTEX_TEXTURE = "vt";
const static Sx::String OBJ_VERTEX = "v";
const static Sx::String OBJ_MATERIAL_LIBRARY = "mtllib";
const static Sx::String OBJ_USE_MATERIAL = "usemtl";

Sx::Graphics::File::OBJ_File::OBJ_File() {
	this->fileDefinition = nullptr;
}

Sx::Graphics::File::OBJ_File::OBJ_File(const String& name, const Util::VectorArray& vertices, const Util::VectorArray& normals, const Util::VectorArray& textureCoords, const Util::IndexArray& indices, const Util::IndexArray& groupIndices, const Util::IndexArray& materialIndices, const Util::IndexArray& smoothingGroupIndices, unsigned int faceDegree) {
	this->fileDefinition = new OBJ_FileDefinition();
	OBJ_Mesh* objMesh = new OBJ_Mesh(name);

	Util::Copy(objMesh->vertices, vertices);
	Util::Copy(objMesh->normals, normals);
	Util::Copy(objMesh->textureCoordinates, textureCoords);

	unsigned int index = 0, faceCount = indices.size() / faceDegree;
	for ( unsigned int i = 0; i < faceCount; i++ ) {
		OBJ_Face curFace;

		if ( faceDegree == 3 ) curFace.faceType = Math::TRIANGLE;
		else if ( faceDegree == 4 ) curFace.faceType = Math::QUAD;
		else curFace.faceType = Math::POLYGON;

		if ( groupIndices.size() == faceCount ) curFace.groupIndex = groupIndices[i];
		if ( materialIndices.size() == faceCount ) curFace.materialIndex = materialIndices[i];
		if ( smoothingGroupIndices.size() == faceCount ) curFace.smoothingGroupIndex = smoothingGroupIndices[i];

		for ( unsigned int j = 0; j < faceDegree; j++ )
			curFace.normalIndices.add(indices[index+j]);

		for ( unsigned int j = 0; j < faceDegree; j++ )
			curFace.vertexIndices.add(indices[index+j]);

		for ( unsigned int j = 0; j < faceDegree; j++ )
			curFace.textureIndices.add(indices[index+j]);

		objMesh->faces.addLast(curFace);
		index += faceDegree;
	}

	this->fileDefinition->addMesh(objMesh);
}

Sx::Graphics::File::OBJ_File::~OBJ_File() {
	delete this->fileDefinition;
}

Sx::Graphics::File::OBJ_FileDefinition const * const Sx::Graphics::File::OBJ_File::getDefinition() const {
	return this->fileDefinition;
}

/*
 * Parses an OBJ comment and add its to the list of collected comments.
 */
bool ParseObjComment(Sx::Util::Scanner& scanner, Sx::Graphics::File::OBJ_FileDefinition* fileDef) {
	fileDef->comments.addLast(scanner.nextLine());
	return true;
}

/*
 * Parses a vertex line from an OBJ file:
 * v x y z
 * 
 * Example:
 * v 1.00 2.00 3.00
 */
bool ParseObjVertex(Sx::Util::Scanner& scanner, 
					Sx::Graphics::File::OBJ_FileDefinition* fileDef, 
					Sx::Graphics::File::OBJ_Mesh*& curMesh) {
	//--------------------------------------------------------------------------
	// If there is no object defined in the OBJ file to add this vertex to, add 
	// it to a new default object.
	//--------------------------------------------------------------------------
	if ( curMesh == nullptr ) {
		fileDef->addMesh();
		curMesh = fileDef->getLastMesh();
	}

	Eigen::Vector3f vertex;
	vertex.x() = scanner.nextFloat();
	vertex.y() = scanner.nextFloat();
	vertex.z() = scanner.nextFloat();

	//--------------------------------------------------------------------------
	// Some OBJ vertices contain color information for each vertex. This
	// additional information can be added to each vertex after the definition
	// of the vertex: v x y z cx cy cz
	//--------------------------------------------------------------------------
	if ( scanner.hasNext() ) {
		Eigen::Vector3f color;
		color.x() = scanner.nextFloat();
		color.y() = scanner.nextFloat();
		color.z() = scanner.nextFloat();
		curMesh->colors.add(color);
	}

	//--------------------------------------------------------------------------
	// Get the mesh currently being created and add the vertex to it.
	//--------------------------------------------------------------------------
	curMesh->vertices.add(vertex);
	return true;
}

/*
 * Parses a vertex texture coordinate line from an OBJ file:
 * vt tx ty tz
 * 
 * Example:
 * v 1.00 1.00 0.00
 */
bool ParseObjTextureCoordinate(Sx::Util::Scanner& scanner, 
							   Sx::Graphics::File::OBJ_FileDefinition* fileDef, 
							   Sx::Graphics::File::OBJ_Mesh*& curMesh) {
	//--------------------------------------------------------------------------
	// If there is no object defined in the OBJ file to, add this texture
	// coordinate to a new default object.
	//--------------------------------------------------------------------------
	if ( curMesh == nullptr ) {
		fileDef->addMesh();
		curMesh = fileDef->getLastMesh();
	}

	Eigen::Vector3f textureCoordinate;
	textureCoordinate.x() = scanner.nextFloat();
	textureCoordinate.y() = scanner.nextFloat();
	textureCoordinate.z() = scanner.nextFloat();

	//--------------------------------------------------------------------------
	// Get the mesh currently being created and add the texture coordinate 
	// to it.
	//--------------------------------------------------------------------------
	curMesh->textureCoordinates.add(textureCoordinate);
	return true;
}

/*
 * Parses a vertex normal line from an OBJ file:
 * vn xn yn zn
 * 
 * Example:
 * v 0.0 0.0 0.0
 */
bool ParseObjNormal(Sx::Util::Scanner& scanner, 
					Sx::Graphics::File::OBJ_FileDefinition* fileDef, 
					Sx::Graphics::File::OBJ_Mesh*& curMesh) {
	//--------------------------------------------------------------------------
	// If there is no object defined in the OBJ file to add this normal to, add 
	// it to a new default object.
	//--------------------------------------------------------------------------
	if ( curMesh == nullptr ) {
		fileDef->addMesh();
		curMesh = fileDef->getLastMesh();
	}

	Eigen::Vector3f normal;
	normal.x() = scanner.nextFloat();
	normal.y() = scanner.nextFloat();
	normal.z() = scanner.nextFloat();

	//--------------------------------------------------------------------------
	// Get the mesh currently being created and add the normal to it.
	//--------------------------------------------------------------------------
	curMesh->normals.add(normal);
	return true;
}

/*
 * Adds a new mesh to the OBJ file definition. All vertices, normals, texture 
 * coords, and faces parsed after this operation will now be added to this new 
 * mesh. Upon completion the state of the OBJ parser has changed and will now 
 * construct a new mesh with the given name until either the end of the OBJ file 
 * is reached or a new object is defined in the same OBJ file.
 */
bool ParseObjObject(Sx::Util::Scanner& scanner, 
					Sx::Graphics::File::OBJ_FileDefinition* fileDef, 
					Sx::Graphics::File::OBJ_Mesh*& curMesh,
					Sx::Graphics::File::OBJ_GroupIndex& curGroupIndex,
					Sx::Graphics::File::OBJ_SmoothingGroupIndex& curSmoothingGroupIndex,
					Sx::Graphics::File::OBJ_MaterialIndex& curMaterialIndex) {

	fileDef->addMesh(scanner.next());

	//--------------------------------------------------------------------------
	// Update the curMesh pointer to point to the newly added OBJ mesh.
	//--------------------------------------------------------------------------
	curMesh = fileDef->getLastMesh();

	//--------------------------------------------------------------------------
	// The group index is reset for each new mesh.
	//--------------------------------------------------------------------------
	curSmoothingGroupIndex = 0;

	return true;
}

/*
 * Parses a smoothing group index from the OBJ file. 0 represents the state
 * of no smoothing group 'off'. Valid smoothing groups start at index 1.
 */
bool ParseObjSmoothingGroup(Sx::Util::Scanner& scanner, 
							Sx::Graphics::File::OBJ_SmoothingGroupIndex& curSmoothingGroupIndex) {
	//--------------------------------------------------------------------------
	// Either the smoothing group is a valid integer or it is represented
	// by the string "off" which is represented by 0 for the smoothing
	// group index. Both of these cases are handled by parsing the next int.
	// If the string is off the resulting parsed int will be 0, otherwise it
	// will be a valid smoothing group index >= 1.
	//--------------------------------------------------------------------------
	curSmoothingGroupIndex = scanner.nextInt();
	return true;
}

/*
 * This function parses an OBJ group which new vertices will be associated with.
 * Some exporters however use the group identifier to signify that a new object
 * is starting. If this is the case this function behaves similar to the parse
 * obj object function in that it creates a new object which all vertices,
 * normals, and texture coordinates will be added to after this function 
 * completes. For example:
 *  g groupName // Defines a new group in the OBJ file named 'groupName'
 *  g           // Defines a new object in the OBJ file with a default name
 */
bool ParseObjGroup(Sx::Util::Scanner& scanner, Sx::Graphics::File::OBJ_FileDefinition* fileDef, Sx::Graphics::File::OBJ_Mesh*& curMesh, Sx::Graphics::File::OBJ_GroupIndex& curGroupIndex, Sx::Graphics::File::OBJ_SmoothingGroupIndex& curSmoothingGroupIndex) {
	//--------------------------------------------------------------------------
	// Associate this group name with the current group index so that for
	// every face that has this group index, the groups name can be found
	// using the groups map.
	//--------------------------------------------------------------------------
	Sx::String groupName = scanner.next();

	//--------------------------------------------------------------------------
	// Some OBJ files utilize the group id with no name to identify a new
	// object (3ds Max 9). If this is the case add a new mesh.
	//--------------------------------------------------------------------------
	if ( groupName.length() == 0 ) {
		fileDef->addMesh(scanner.next());
		curMesh = fileDef->getLastMesh();
		curSmoothingGroupIndex = 0;
	}
	else {
		curGroupIndex++;
		fileDef->groups.add(curGroupIndex, groupName);
	}
	return true;
}

/*
 * This function simply parses the material file name and adds it to the
 * list of material libraries defined in the OBJ file.
 */
bool ParseObjMaterialLibraryName(Sx::Util::Scanner& scanner, 
								 Sx::Graphics::File::OBJ_FileDefinition* fileDef) {
	fileDef->materialLibraries.add(scanner.next());
	return true;
}

/*
 * This function parses the name of a material that can be used by any face
 * in the OBJ file. This function extracts the material name and adds it to
 * the materials map in the file definition and provides a unique index
 * to which it can be referred to by faces.
 */
bool PaseObjMaterialName(Sx::Util::Scanner& scanner, 
						 Sx::Graphics::File::OBJ_FileDefinition* fileDef, 
						 Sx::Graphics::File::OBJ_MaterialIndex& curMaterialIndex) {
	//--------------------------------------------------------------------------
	// Associate this material name with the current material index so that for
	// every face that has this material index, the materials name can be found
	// using the materials map.
	//--------------------------------------------------------------------------
	curMaterialIndex++;
	fileDef->materials.add(curMaterialIndex, scanner.next());
	return true;
}

/*
 * This function parses a face 'node' defined in an OBJ file. Each set of 
 * indices: vertex, vertex/texture, vertex//normal, or vertex/texture/normal
 * are parsed and returned with a proper 0 starting index. (OBJ indices start
 * at OBJ_INDEX_OFFSET).
 */
bool ParseObjNode(const Sx::String& node, int& vertexIndex, int& textureCoordIndex, int& normalIndex) {
	unsigned int slashCount = Sx::String::Occurances(node, OBJ_NODE_DELIMITER);
	Sx::Util::Scanner scanner(node, OBJ_NODE_DELIMITER);

	//--------------------------------------------------------------------------
	// Depending on how many NODE_DELIMITERS there are in this node, extract
	// the information accordingly.
	//--------------------------------------------------------------------------
	if ( slashCount == 0 ) {
		vertexIndex = scanner.nextInt() - OBJ_INDEX_OFFSET;
		return true;
	}
	else if ( slashCount == 1 ) {
		vertexIndex = scanner.nextInt() - OBJ_INDEX_OFFSET;
		normalIndex = 0;
		textureCoordIndex = scanner.nextInt() - OBJ_INDEX_OFFSET;
		return true;
	}
	else if ( slashCount == 2 ) {
		vertexIndex = scanner.nextInt() - OBJ_INDEX_OFFSET;
		textureCoordIndex = scanner.nextInt() - OBJ_INDEX_OFFSET;
	
		//----------------------------------------------------------------------
		// Rely on the fact that OBJ stores indices starting at 1 so if the end 
		// of the string is reached during this call the reported normalIndex 
		// will be 0. If it is 0 then this node only contains vertex and
		// texture information. Otherwise if normalIndex >= 1 then it is a valid
		// normal index and should be added.
		//----------------------------------------------------------------------
		normalIndex = scanner.nextInt();
		if ( normalIndex == 0 ) {
			normalIndex = textureCoordIndex;
			textureCoordIndex = OBJ_INVALID_FACE_INDEX;
		}
		else normalIndex -= OBJ_INDEX_OFFSET;
		return true;
	}
	else {
		std::cout << "[OBJ_File:ParseObjNode] Error: Invalid face node encountered. Aborting." << std::endl;
		return false;
	}

	return true;
}

bool ValidFace(const Sx::Graphics::File::OBJ_Face& face) {
	for ( unsigned int i = 0; i < face.vertexIndices.size(); i++ )
		if ( face.vertexIndices[i] < 0 ) return false;
	for ( unsigned int i = 0; i < face.textureIndices.size(); i++ )
		if ( face.textureIndices[i] < 0 ) return false;
	for ( unsigned int i = 0; i < face.normalIndices.size(); i++ )
		if ( face.normalIndices[i] < 0 ) return false;
	return true;
}

bool ParseObjFace(Sx::Util::Scanner& scanner, 
				  Sx::Graphics::File::OBJ_FileDefinition* fileDef,
				  Sx::Graphics::File::OBJ_GroupIndex& curGroupIndex, 
				  Sx::Graphics::File::OBJ_SmoothingGroupIndex& curSmoothingGroupIndex,
				  Sx::Graphics::File::OBJ_MaterialIndex& curMaterialIndex) {
	//--------------------------------------------------------------------------
	// Iterate through each face node that defines the face. Since
	// a face can have n sides, and each node is separated by a
	// space, each node on the line is parsed and added to the
	// face.
	//--------------------------------------------------------------------------
	Sx::String node;
	Sx::Graphics::File::OBJ_Face face;
	unsigned int nodeCount = 0;

	//--------------------------------------------------------------------------
	// These are initialized to NO_INDEX so that if a face does not contain a 
	// normal index or texture coordinate index it will not be added to the 
	// normal or texture coordinate arrays (respectively) in the face.
	//--------------------------------------------------------------------------
	int vertexIndex = OBJ_INVALID_FACE_INDEX;
	int textureCoordIndex = OBJ_INVALID_FACE_INDEX;
	int normalIndex = OBJ_INVALID_FACE_INDEX;

	while ( scanner.hasNext() ) {
		node = scanner.next();
		ParseObjNode(node, vertexIndex, textureCoordIndex, normalIndex);

		//----------------------------------------------------------------------
		// If any of the vertex, normal, or textureCoord indices could not be
		// read from the file, this will cause a problem for any systems 
		// implemented to compress or render the data so abort.
		//----------------------------------------------------------------------
		if ( vertexIndex < 0 ) {
			std::cout << "[OBJ_File:ParseObjFile] Error: Invalid vertex index. Aborting." << std::endl;
			return false;
		}

		//----------------------------------------------------------------------
		// Some OBJ files contain vertex, vertex/texture, vertex//normal, or
		// vertex/texture/normal information. Depending on which one is provided
		// only the available information will be added.
		//----------------------------------------------------------------------
		if ( vertexIndex >= 0 ) face.vertexIndices.add(vertexIndex);
		else face.vertexIndices.add(0);

		if ( textureCoordIndex >= 0 ) face.textureIndices.add(textureCoordIndex);
		else face.textureIndices.add(0);

		if ( normalIndex >= 0 ) face.normalIndices.add(normalIndex);
		else face.normalIndices.add(0);
		nodeCount++;
	}

	//--------------------------------------------------------------------------
	// This error should not prevent the parsing process since an object can be
	// loaded, even if some faces are corrupt. If all faces are corrupt or
	// improperly formatted then the object will contain no faces.
	//--------------------------------------------------------------------------
	if ( nodeCount <= 2 ) {
		std::cout << "[Obj_File:ParseObjFace] Warning: Invalid OBJ face. Each OBJ face must have at least 3 sides. Ignoring face." << std::endl;
		return true;
	}

	//--------------------------------------------------------------------------
	// Determine the type of geometric primitive that describes the
	// parsed face (based on how many sides it contains).
	//--------------------------------------------------------------------------
	if ( nodeCount == 3 ) face.faceType = Sx::Math::TRIANGLE;
	if ( nodeCount == 4 ) face.faceType = Sx::Math::QUAD;
	if ( nodeCount >= 5 ) face.faceType = Sx::Math::POLYGON;

	//--------------------------------------------------------------------------
	// Set the current group, smoothing, and material indices for this face.
	//--------------------------------------------------------------------------
	face.groupIndex = curGroupIndex;
	face.smoothingGroupIndex = curSmoothingGroupIndex;
	face.materialIndex = curMaterialIndex;

	if ( ValidFace(face) ) fileDef->getLastMesh()->faces.add(face);
	return true;
}

/*
 * This function and all sub-functions used to parse an OBJ file utilize the 
 * strtok function due to speed requirements. If this function returns true,
 * parsing the line of the file was completed successfully; otherwise an
 * unrecoverable error was encountered. This series of parsing functions are
 * intended to be efficient however, clarity will not be sacrificed since
 * parsing any text file is a slow process anyways.
 */
bool ParseObjFileLine(Sx::Graphics::File::OBJ_FileDefinition* fileDef, 
					  const Sx::String& line, 
					  Sx::Graphics::File::OBJ_Mesh*& curMesh,
					  Sx::Graphics::File::OBJ_GroupIndex& curGroupIndex,
					  Sx::Graphics::File::OBJ_SmoothingGroupIndex& curSmoothingGroupIndex,
					  Sx::Graphics::File::OBJ_MaterialIndex& curMaterialIndex) {

	unsigned int len = line.length();

	//--------------------------------------------------------------------------
	// Empty OBJ file line.
	//--------------------------------------------------------------------------
	if ( len == 0 ) return true;

	Sx::Util::Scanner scanner(line, OBJ_DELIMITER);
	Sx::String id = scanner.next();

	//--------------------------------------------------------------------------
	// Depending on the component being parsed from the OBJ file, call an 
	// appropriate function to extract the information and store it in the 
	// appropriate location.
	//--------------------------------------------------------------------------
	if ( id[0] == OBJ_COMMENT ) return ParseObjComment(scanner, fileDef);
	else if ( id.compare(OBJ_VERTEX) == 0 ) return ParseObjVertex(scanner, fileDef, curMesh);
	else if ( id.compare(OBJ_VERTEX_TEXTURE) == 0 ) return ParseObjTextureCoordinate(scanner, fileDef, curMesh);
	else if ( id.compare(OBJ_VERTEX_NORMAL) == 0 ) return ParseObjNormal(scanner, fileDef, curMesh);
	else if ( id.compare(OBJ_FACE) == 0 ) return ParseObjFace(scanner, fileDef, curGroupIndex, curSmoothingGroupIndex, curMaterialIndex);
	else if ( id.compare(OBJ_SMOOTHING_GROUP) == 0 ) return ParseObjSmoothingGroup(scanner, curSmoothingGroupIndex);
	else if ( id.compare(OBJ_GROUP) == 0 ) return ParseObjGroup(scanner, fileDef, curMesh, curGroupIndex, curSmoothingGroupIndex);
	else if ( id.compare(OBJ_OBJECT) == 0 ) return ParseObjObject(scanner, fileDef, curMesh, curGroupIndex, curSmoothingGroupIndex, curMaterialIndex);
	else if ( id.compare(OBJ_MATERIAL_LIBRARY) == 0 ) return ParseObjMaterialLibraryName(scanner, fileDef);
	else if ( id.compare(OBJ_USE_MATERIAL) == 0 ) return PaseObjMaterialName(scanner, fileDef, curMaterialIndex);
	else std::cout << "[OBJ_File:ParseObjFileLine] Warning: Encountered an unrecognized OBJ file line. Ignoring." << std::endl;

	return true;
}

bool EmptyMesh(Sx::Graphics::File::OBJ_Mesh* mesh) {
	if ( mesh->vertices.size() == 0 ) return true;
	return false;
}

/*
 * This function will load an OBJ file and construct all of the meshes contained
 * within the OBJ file. This function will return true if the file is loaded
 * successfully; otherwise an error was encountered and the parsing process
 * could not ignore the error.
 */
bool Sx::Graphics::File::OBJ_File::load(const String& filename) {
	//--------------------------------------------------------------------------
	// Create a line parser to read the OBJ Ascii file one line at a time.
	//--------------------------------------------------------------------------
	Sx::File::LineParser lineParser;
	if ( !lineParser.open(filename) ) {
		std::cout << "[OBJ_File:Load] Error: Could not open file: " << filename << std::endl;
		return false;
	}

	//--------------------------------------------------------------------------
	// Initialize the initial state of the OBJ parser.
	//--------------------------------------------------------------------------
	if ( this->fileDefinition == nullptr ) this->fileDefinition = new OBJ_FileDefinition();
	else this->fileDefinition->clear();

	//--------------------------------------------------------------------------
	// As these indices are used by reading lines in the OBJ file, they should
	// be updated automatically.
	//--------------------------------------------------------------------------
	OBJ_SmoothingGroupIndex curSmoothingGroup = 0;
	OBJ_GroupIndex curGroupIndex = 0;
	OBJ_MaterialIndex curMaterialIndex = 0;
	OBJ_Mesh* curMesh = this->fileDefinition->getLastMesh();

	this->fileDefinition->materials.add(curMaterialIndex, OBJ_NO_MATERIAL);
	this->fileDefinition->groups.add(curGroupIndex, OBJ_NO_GROUP);

	//--------------------------------------------------------------------------
	// This adds additional time to the actual parsing process; however it
	// provides the user with a much better idea about how long the OBJ import
	// is going to take since this will show a good estimate of the total
	// size of the OBJ file to load. This simply counts the number of newlines
	// in the OBJ file to load. This works because OBJ files are defined by
	// line identifiers, therefore the total number of lines gives a good
	// approximation of the total amount of work.
	//--------------------------------------------------------------------------
	this->progressMonitor.setRange(0, 0);
	this->progressMonitor.setTitle("Scanning OBJ File: " + filename);
	this->progressMonitor.setDescription("Scanning OBJ File: " + filename);
	std::ifstream inFile(filename.toAscii()); 
	unsigned int lines = static_cast<unsigned int>(std::count(std::istreambuf_iterator<char>(inFile), std::istreambuf_iterator<char>(), '\n'));

	//--------------------------------------------------------------------------
	// Parse the OBJ file one line at a time constructing each mesh contained
	// in the file. The parsing process is modeled as a state machine. Different
	// OBJ file commands change the state of how the current mesh will be
	// constructed.
	//--------------------------------------------------------------------------
	String curLine;
	unsigned int curLineNumber = 1;
	this->progressMonitor.setRange(0, 100);
	this->progressMonitor.setDescription("Loading Obj File: " + filename + "\nDiscovering Component Definitions...");
	while ( lineParser.hasNext() ) {
		curLine = lineParser.nextLine();

		if ( ParseObjFileLine(this->fileDefinition, curLine, curMesh, curGroupIndex, curSmoothingGroup, curMaterialIndex) == false ) {
			std::cout << "[OBJ_File:Load] Error: Failed to parse an OBJ file line and cannot recover." << std::endl;
			std::cout << "  Aborting OBJ file parsing process at line: " << curLine << std::endl;
			return false;
		}

		curLineNumber++;
		this->progressMonitor.setValue(static_cast<unsigned int>(((static_cast<float>(curLineNumber) / static_cast<float>(lines)) * 100.0f) + 1.0f));

		//----------------------------------------------------------------------
		// Cancel Operation
		//----------------------------------------------------------------------
		if ( this->progressMonitor.canceled() == true ) {
			lineParser.close();
			return false;
		}
	}

	lineParser.close();

	//--------------------------------------------------------------------------
	// TODO: Remove any meshes that might have been added with no vertices, 
	// normals, or texture coordinates added. These are just considered 
	// empty meshes.
	//--------------------------------------------------------------------------
	this->fileDefinition->meshes.removeIf(EmptyMesh);
	return true;
}

/*
 * This function defines the header that is printed at the top of each OBJ file
 * that is saved.
 */
bool SaveHeader(std::ostream& out) {
	out << "# Sx Library Wavefront Obj Version 1.0 April 2013" << std::endl;
	out << std::endl;
	return true;
}

bool SaveMaterialLibraries(std::ostream& out, Sx::Graphics::File::OBJ_File const * const objFile) {
	if ( objFile->getDefinition()->materialLibraries.size() > 0 )
		out << "# Obj Material Libraries" << std::endl;
	for ( unsigned int i = 0; i < objFile->getDefinition()->materialLibraries.size(); i++ )
		out << OBJ_MATERIAL_LIBRARY << OBJ_DELIMITER << objFile->getDefinition()->materialLibraries[i] << std::endl;
	out << std::endl;
	return true;
}

bool Sx::Graphics::File::OBJ_File::saveObject(std::ostream& out, unsigned int meshIndex, bool writeNormals, bool writeTextureCoords) {
	if ( meshIndex >= this->getDefinition()->meshes.size() ) {
		std::cout << "[OBJ_File:saveObject] Error: Mesh index out of bounds: " << meshIndex << std::endl;
		return false;
	}

	Sx::Graphics::File::OBJ_Mesh const * const mesh = this->getDefinition()->meshes[meshIndex];

	if ( mesh == nullptr ) {
		std::cout << "[OBJ_File:saveObject] Error: Null mesh cannot be saved." << std::endl;
		return false;
	}

	out << "# Obj Mesh Number: " << meshIndex + 1 << std::endl;
	//--------------------------------------------------------------------------
	// Write the name of the mesh if it has one.
	//--------------------------------------------------------------------------
	if ( mesh->name.compare(Sx::Graphics::File::OBJ_NO_MESH_NAME) != 0 ) out << OBJ_OBJECT << OBJ_DELIMITER << mesh->name << std::endl;

	//--------------------------------------------------------------------------
	// Write the vertices of the mesh
	//--------------------------------------------------------------------------
	String message = "Writing Object Vertices (";
	message.append(String::ValueOf(static_cast<int>(mesh->vertices.size())));
	message.append(") ...");

	this->progressMonitor.setDescription(message);
	this->progressMonitor.setRange(0, 100);
	unsigned int numVertices = mesh->vertices.size();

	for ( unsigned int i = 0; i < numVertices; i++ ) {
		out << OBJ_VERTEX << OBJ_DELIMITER << mesh->vertices[i].x() << OBJ_DELIMITER << mesh->vertices[i].y() << OBJ_DELIMITER << mesh->vertices[i].z() << std::endl;
		this->progressMonitor.setValue(static_cast<unsigned int>(((static_cast<float>(i) / static_cast<float>(numVertices)) * 100.0f) + 1.0f));
	}

	out << "# " << mesh->vertices.size() << " vertices" << std::endl;
	out << std::endl;

	//--------------------------------------------------------------------------
	// Write the texture coordinates of the mesh, if included.
	//--------------------------------------------------------------------------
	if ( writeTextureCoords == true ) {
		message = "Writing Object Texture Coordinates (";
		message.append(String::ValueOf(static_cast<int>(mesh->textureCoordinates.size())));
		message.append(") ...");

		this->progressMonitor.setDescription(message);
		this->progressMonitor.setRange(0, 100);
		unsigned int numTextureCoords = mesh->textureCoordinates.size();

		for ( unsigned int i = 0; i < numTextureCoords; i++ ) {
			out << OBJ_VERTEX_TEXTURE << OBJ_DELIMITER << mesh->textureCoordinates[i].x() << OBJ_DELIMITER << mesh->textureCoordinates[i].y() << OBJ_DELIMITER << mesh->textureCoordinates[i].z() << std::endl;
			this->progressMonitor.setValue(static_cast<unsigned int>(((static_cast<float>(i) / static_cast<float>(numTextureCoords)) * 100.0f) + 1.0f));
		}

		if ( mesh->textureCoordinates.size() > 0 ) {
			out << "# " << mesh->textureCoordinates.size() << " texture coordinates" << std::endl;
			out << std::endl;
		}
	}

	//--------------------------------------------------------------------------
	// Write the normals of the mesh, if included.
	//--------------------------------------------------------------------------
	if ( writeNormals == true ) {
		message = "Writing Object Normals (";
		message.append(String::ValueOf(static_cast<int>(mesh->normals.size())));
		message.append(") ...");

		this->progressMonitor.setDescription(message);
		this->progressMonitor.setRange(0, 100);
		unsigned int numNormals = mesh->normals.size();

		for ( unsigned int i = 0; i < mesh->normals.size(); i++ ) {
			out << OBJ_VERTEX_NORMAL << OBJ_DELIMITER << mesh->normals[i].x() << OBJ_DELIMITER << mesh->normals[i].y() << OBJ_DELIMITER << mesh->normals[i].z() << std::endl;
			this->progressMonitor.setValue(static_cast<unsigned int>(((static_cast<float>(i) / static_cast<float>(numNormals)) * 100.0f) + 1.0f));
		}

		if ( mesh->normals.size() > 0 ) {
			out << "# " << mesh->normals.size() << " vertex normals" << std::endl;
			out << std::endl;
		}
	}

	//--------------------------------------------------------------------------
	// Write the faces of the mesh.
	// There is no material that is defined initially. If a face states that
	// it has an applied material then the current material index is set to
	// that index so it can be written to the file.
	//--------------------------------------------------------------------------
	unsigned int lastMaterialIndex = 0;
	unsigned int lastGroupIndex = 0;
	unsigned int lastSmoothingGroupIndex = 0;
	Sx::Graphics::File::OBJ_Face face;

	message = "Writing Object Faces (";
	message.append(String::ValueOf(static_cast<int>(mesh->faces.size())));
	message.append(") ...");

	this->progressMonitor.setDescription(message);
	this->progressMonitor.setRange(0, 100);
	unsigned int numFaces = mesh->faces.size();

	for ( unsigned int i = 0; i < numFaces; i++ ) {
		face = mesh->faces[i];
		this->progressMonitor.setValue(static_cast<unsigned int>(((static_cast<float>(i) / static_cast<float>(numFaces)) * 100.0f) + 1.0f));

		//----------------------------------------------------------------------
		// If the face belongs to a group, define the group. If the faces group
		// index is 0 that means it belongs to no group.
		//----------------------------------------------------------------------
		if ( face.groupIndex != 0 && face.groupIndex != lastGroupIndex ) {
			out << OBJ_GROUP << OBJ_DELIMITER << this->getFaceGroup(face) << std::endl;
			lastGroupIndex = face.groupIndex;
		}

		//----------------------------------------------------------------------
		// If the face has a material, define the material. If the faces 
		// material index is 0 that means it has no material.
		//----------------------------------------------------------------------
		if ( face.materialIndex != 0 && face.materialIndex != lastMaterialIndex ) {
			out << OBJ_USE_MATERIAL << OBJ_DELIMITER << this->getFaceMaterial(face) << std::endl;
			lastMaterialIndex = face.materialIndex;
		}

		//----------------------------------------------------------------------
		// If a face does not belong to a smoothing group then it is simply not
		// included.
		//----------------------------------------------------------------------
		if ( face.smoothingGroupIndex != 0 && face.smoothingGroupIndex != lastSmoothingGroupIndex ) {
			out << OBJ_SMOOTHING_GROUP << OBJ_DELIMITER << face.smoothingGroupIndex << std::endl;
			lastSmoothingGroupIndex = face.smoothingGroupIndex;
		}

		//----------------------------------------------------------------------
		// Write the face indices of the face. Vertices are required, therefore
		// the size of that index array is used for indexing through all of the
		// indice arrays.
		//----------------------------------------------------------------------
		out << OBJ_FACE << OBJ_DELIMITER;
		for ( unsigned int i = 0; i < face.vertexIndices.size(); i++ ) {
			//------------------------------------------------------------------
			// Depending on which vertex components are included (normal, 
			// texture coordinate), write the proper definition of each face 
			// node.
			//------------------------------------------------------------------
			out << face.vertexIndices[i] + OBJ_INDEX_OFFSET;
			if ( writeTextureCoords == true && writeNormals == true ) {
				if ( face.textureIndices.size() > 0 && face.normalIndices.size() > 0 )
					out << OBJ_NODE_DELIMITER << face.textureIndices[i] + OBJ_INDEX_OFFSET << OBJ_NODE_DELIMITER << face.normalIndices[i] + OBJ_INDEX_OFFSET;
				else if ( face.textureIndices.size() > 0 && face.normalIndices.size() == 0 )
					out << OBJ_NODE_DELIMITER << face.textureIndices[i] + OBJ_INDEX_OFFSET;
			}
			else if ( writeTextureCoords == true && writeNormals == false ) {
				if ( face.textureIndices.size() > 0 )
					out << OBJ_NODE_DELIMITER << face.textureIndices[i] + OBJ_INDEX_OFFSET;
			}
			else {
				if ( face.normalIndices.size() > 0 )
					out << OBJ_NODE_DELIMITER << OBJ_NODE_DELIMITER << face.normalIndices[i] + OBJ_INDEX_OFFSET;
			}
			
			out << OBJ_DELIMITER;
		}
		out << std::endl;
	}

	out << std::endl;
	return true;
}

bool Sx::Graphics::File::OBJ_File::saveObjectCompressed(std::ostream& out, unsigned int meshIndex, bool writeNormals, bool writeTextureCoords) {
	std::cout << "[OBJ_File:saveObjectCompressed] Error: Not supported." << std::endl;
	return false;
}

bool Sx::Graphics::File::OBJ_File::save(const String& filename, bool writeNormals, bool writeTextureCoords) {
	if ( this->fileDefinition == nullptr ) {
		std::cout << "[OBJ_File:save] Error: Null file definition." << std::endl;
		return false;
	}

	if ( filename.length() == 0 ) {
		std::cout << "[OBJ_File:save] Error: Cannot save OBJ file with filename length of 0." << std::endl;
		return false;
	}

	std::ofstream out;
	out.open(filename.toAscii());

	if ( out.is_open() == false ) {
		std::cout << "[OBJ_File:save] Error: Cannot open file: " << filename << std::endl;
		return false;
	}

	this->progressMonitor.setTitle("Exporting OBJ File");
	out << std::setprecision(8);
	SaveHeader(out);

	this->progressMonitor.setDescription("Writing OBJ Material Libraries");
	SaveMaterialLibraries(out, this);

	//--------------------------------------------------------------------------
	// Save all meshes contained in the file.
	//--------------------------------------------------------------------------
	if ( this->compressOnSave == true ) {
		this->progressMonitor.setDescription("Writing Compressed OBJ Meshes...");
		for ( unsigned int i = 0; i < this->fileDefinition->meshes.size(); i++ ) {
			if ( this->fileDefinition->meshes[i]->normals.size() == 0 )
				writeNormals = false;
			if ( this->fileDefinition->meshes[i]->textureCoordinates.size() == 0 )
				writeTextureCoords = false;
			if ( !this->saveObjectCompressed(out, i, writeNormals, writeTextureCoords) )
				std::cout << "[OBJ_File:save] Warning: Error writing compressed mesh: " << i << " to file: " << filename << std::endl;
		}
	}
	else {
		this->progressMonitor.setDescription("Writing OBJ Meshes...");
		for ( unsigned int i = 0; i < this->fileDefinition->meshes.size(); i++ ) {
			if ( this->fileDefinition->meshes[i]->normals.size() == 0 )
				writeNormals = false;
			if ( this->fileDefinition->meshes[i]->textureCoordinates.size() == 0 )
				writeTextureCoords = false;
			if ( !this->saveObject(out, i, writeNormals, writeTextureCoords) )
				std::cout << "[OBJ_File:save] Warning: Error writing mesh: " << i << " to file: " << filename << std::endl;
		}
	}

	out.close();
	return true;
}

void Sx::Graphics::File::OBJ_File::setCompressOnSave(bool compress) {
	this->compressOnSave = compress;
}

Sx::String Sx::Graphics::File::OBJ_File::getFaceMaterial(const OBJ_Face& face) const {
	return this->fileDefinition->materials.getValue(face.materialIndex);
}

Sx::String Sx::Graphics::File::OBJ_File::getFaceGroup(const OBJ_Face& face) const {
	return this->fileDefinition->groups.getValue(face.groupIndex);
}
